<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="pytest（二）fixture固件">
<meta name="twitter:description" content="<p>pytest中fixture固件的用法。</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/pytest-fixture.html"/>
<meta property="og:title" content="来玩魔王的咚 | pytest（二）fixture固件" />
<meta property="og:description" content="<p>pytest中fixture固件的用法。</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>pytest中fixture固件的用法。</p>" />

    <meta name="tags" content="测试" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li><a href="/category/go.html">Go</a></li>
                            <li><a href="/category/linux.html">Linux</a></li>
                            <li class="active"><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2022-03-17 10:28:00+08:00">
                <script>document.write(moment('2022-03-17 10:28:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2022-03-17 10:28:00+08:00">
                <script>document.write(moment('2022-03-21 12:05:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            pytest（二）fixture固件
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#fixture-gu-jian">Fixture 固件</a><ul>
<li><a href="#gu-jian-ke-yi-shi-yong-ling-yi-ge-gu-jian">固件可以使用另一个固件</a></li>
<li><a href="#gu-jian-ke-chong-fu-shi-yong">固件可重复使用</a></li>
<li><a href="#yi-ci-ke-yi-shi-yong-duo-ge-gu-jian">一次可以使用多个固件</a></li>
<li><a href="#yi-ci-ce-shi-shi-yong-gu-jian-duo-ci">一次测试使用固件多次</a></li>
</ul>
</li>
<li><a href="#zi-dong-gu-jian">自动固件</a></li>
<li><a href="#gu-jian-de-scope-fan-wei">固件的scope范围</a><ul>
<li><a href="#dong-tai-fan-wei">动态范围</a></li>
</ul>
</li>
<li><a href="#gu-jian-qing-li-bu-zhou">固件清理步骤</a><ul>
<li><a href="#yield-tui-jian">yield（推荐）</a></li>
<li><a href="#tian-jia-zhong-jie-qi">添加终结器</a></li>
<li><a href="#an-quan-xiao-hui-gu-jian">安全销毁固件</a></li>
</ul>
</li>
<li><a href="#duo-ge-ce-shi-de-tong-yong-gu-jian">多个测试的通用固件</a></li>
<li><a href="#gu-jian-zhong-tong-guo-shang-xia-wen-chuan-di-bian-liang-zhi">固件中通过上下文传递变量值</a></li>
<li><a href="#ce-shi-han-shu-gei-gu-jian-chuan-zhi-jue-ding-fan-hui-zhi">测试函数给固件传值决定返回值</a></li>
<li><a href="#gong-chang-han-shu-gu-jian">工厂函数固件</a></li>
<li><a href="#parametrizing-fixtures-can-shu-hua-gu-jian">Parametrizing fixtures 参数化固件</a><ul>
<li><a href="#pytestparam">pytest.param()</a></li>
</ul>
</li>
<li><a href="#usefixtures-gu-jian">usefixtures固件</a><ul>
<li><a href="#duo-ge-usefixtures-gu-jian">多个usefixtures固件</a></li>
<li><a href="#mo-kuai-ji-bie-shi-yong">模块级别使用</a></li>
</ul>
</li>
<li><a href="#ge-ge-ji-bie-dui-gu-jian-de-fu-gai">各个级别对固件的覆盖</a><ul>
<li><a href="#wen-jian-jia-ji-bie-fu-gai">文件夹级别覆盖</a></li>
<li><a href="#mo-kuai-ji-bie-fu-gai">模块级别覆盖</a></li>
<li><a href="#fu-gai-gu-jian-de-fan-hui-zhi">覆盖固件的返回值</a></li>
<li><a href="#can-shu-hua-de-gu-jian-he-fei-can-shu-hua-gu-jian-de-hu-xiang-fu-gai-de-qing-kuang">参数化的固件和非参数化固件的互相覆盖的情况</a></li>
</ul>
</li>
</ul>
</div>
<h3 id="fixture-gu-jian"><a class="toclink" href="#fixture-gu-jian">Fixture 固件</a></h3>
<p>fixture 是测试函数需要用到的一些变量或者步骤。变量会在测试函数中被使用。步骤，可能测试函数中不会使用这个返回值，但是需要在测试函数执行前有一个执行的过程。<br>
fixture 实际是一个函数，传入测试函数中拿到的则是函数的返回值。之所以不是直接传值，因为可以对这个函数做更多的控制。比如每次都是一个新的执行结果，或者在指定的范围内fixture的函数只执行一次。<br>
基本的用法如下：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="c1"># 固件生成数据</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">create_data</span><span class="p">():</span>
    <span class="k">return</span> <span class="p">[</span><span class="s1">&#39;Adam&#39;</span><span class="p">,</span> <span class="s1">&#39;Bob&#39;</span><span class="p">]</span>

<span class="c1"># 通过传参使用固件，获取到的是固件的返回值</span>
<span class="k">def</span> <span class="nf">test_adam</span><span class="p">(</span><span class="n">create_data</span><span class="p">):</span>
    <span class="k">assert</span> <span class="s1">&#39;Adam&#39;</span> <span class="ow">in</span> <span class="n">create_data</span>

<span class="c1"># 固件方便复用</span>
<span class="k">def</span> <span class="nf">test_bob</span><span class="p">(</span><span class="n">create_data</span><span class="p">):</span>
    <span class="k">assert</span> <span class="s1">&#39;Bob&#39;</span> <span class="ow">in</span> <span class="n">create_data</span>
</code></pre></div>

<h4 id="gu-jian-ke-yi-shi-yong-ling-yi-ge-gu-jian"><a class="toclink" href="#gu-jian-ke-yi-shi-yong-ling-yi-ge-gu-jian">固件可以使用另一个固件</a></h4>
<p>不仅测试函数可以使用固件，固件也可以使用固件。这样就是嵌套使用了，和测试函数的用法一样，把一个固件作为参数传参给另一个固件。  </p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">first_entry</span><span class="p">():</span>
    <span class="sd">&quot;&quot;&quot;返回数据&quot;&quot;&quot;</span>
    <span class="k">return</span> <span class="s2">&quot;a&quot;</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">order</span><span class="p">(</span><span class="n">first_entry</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;返回列表放入固件返回的数据&quot;&quot;&quot;</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">first_entry</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">test_string</span><span class="p">(</span><span class="n">order</span><span class="p">):</span>
    <span class="n">order</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&quot;b&quot;</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">order</span> <span class="o">==</span> <span class="p">[</span><span class="s2">&quot;a&quot;</span><span class="p">,</span> <span class="s2">&quot;b&quot;</span><span class="p">]</span>
</code></pre></div>

<h4 id="gu-jian-ke-chong-fu-shi-yong"><a class="toclink" href="#gu-jian-ke-chong-fu-shi-yong">固件可重复使用</a></h4>
<p>固件可以重复使用，并且每次用的时候都是独立了，不会受到别处使用的影响。<br>
在上一小节的示例的基础上，再加一个测试参数，向列表中添加另一个元素。固件和在当前测试之前生成新的列表，不会受到别的测试对列表操作的影响：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">first_entry</span><span class="p">():</span>
    <span class="sd">&quot;&quot;&quot;返回数据&quot;&quot;&quot;</span>
    <span class="k">return</span> <span class="s2">&quot;a&quot;</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">order</span><span class="p">(</span><span class="n">first_entry</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;返回列表放入固件返回的数据&quot;&quot;&quot;</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">first_entry</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">test_string</span><span class="p">(</span><span class="n">order</span><span class="p">):</span>
    <span class="n">order</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&quot;b&quot;</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">order</span> <span class="o">==</span> <span class="p">[</span><span class="s2">&quot;a&quot;</span><span class="p">,</span> <span class="s2">&quot;b&quot;</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">test_int</span><span class="p">(</span><span class="n">order</span><span class="p">):</span>
    <span class="n">order</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">order</span> <span class="o">==</span> <span class="p">[</span><span class="s2">&quot;a&quot;</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span>
</code></pre></div>

<h4 id="yi-ci-ke-yi-shi-yong-duo-ge-gu-jian"><a class="toclink" href="#yi-ci-ke-yi-shi-yong-duo-ge-gu-jian">一次可以使用多个固件</a></h4>
<p>固件是作为参数传入的，参数的数量不限，可以传入多个固件使用：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">first_entry</span><span class="p">():</span>
    <span class="k">return</span> <span class="s2">&quot;a&quot;</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">second_entry</span><span class="p">():</span>
    <span class="k">return</span> <span class="mi">2</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">order</span><span class="p">(</span><span class="n">first_entry</span><span class="p">,</span> <span class="n">second_entry</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;这个固件使用了两个固件，向列表添加了两个元素&quot;&quot;&quot;</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">first_entry</span><span class="p">,</span> <span class="n">second_entry</span><span class="p">]</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">expected_list</span><span class="p">():</span>
    <span class="k">return</span> <span class="p">[</span><span class="s2">&quot;a&quot;</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">test_string</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">expected_list</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;这个测试函数使用了两个固件，一个是需要测试的数据，一个是测试期待的数据&quot;&quot;&quot;</span>
    <span class="n">order</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mf">3.0</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">order</span> <span class="o">==</span> <span class="n">expected_list</span>
</code></pre></div>

<h4 id="yi-ci-ce-shi-shi-yong-gu-jian-duo-ci"><a class="toclink" href="#yi-ci-ce-shi-shi-yong-gu-jian-duo-ci">一次测试使用固件多次</a></h4>
<p>在一次测试的过程中，固件可以被多次使用，并且固件的返回值会被缓存。<br>
直接或间接使用固件多次。无法在一个函数里传入两个同名的变量，所以只能直接使用固件一次。通过固件调用固件的方式，实现间接的使用固件。<br>
并且固件的值会被缓存，在之后使用固件的时候，会收到之前操作的影响。  </p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">first_entry</span><span class="p">():</span>
    <span class="k">return</span> <span class="s2">&quot;a&quot;</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">order</span><span class="p">():</span>
    <span class="k">return</span> <span class="p">[]</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">append_first</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">first_entry</span><span class="p">):</span>
    <span class="n">order</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">first_entry</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_string</span><span class="p">(</span><span class="n">append_first</span><span class="p">,</span> <span class="n">order</span><span class="p">,</span> <span class="n">first_entry</span><span class="p">):</span>
    <span class="k">assert</span> <span class="n">order</span> <span class="o">==</span> <span class="p">[</span><span class="n">first_entry</span><span class="p">]</span>
</code></pre></div>

<p>在test_string这个测试函数中，使用了3个固件。第一个append_first会向order固件的空列表中添加一个元素。第二个传入的order会受到第一个固件的影响，传入的不是空列表，而是之前被append_first添加过元素的列表。  </p>
<h3 id="zi-dong-gu-jian"><a class="toclink" href="#zi-dong-gu-jian">自动固件</a></h3>
<p>自动固件可以免传参，所有测试自动会调用这个固件。<br>
下面的两个测试函数都需要先运行append_first，可以把这个固件做成自动使用：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">first_entry</span><span class="p">():</span>
    <span class="k">return</span> <span class="s2">&quot;a&quot;</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">order</span><span class="p">():</span>
    <span class="k">return</span> <span class="p">[]</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">append_first</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">first_entry</span><span class="p">):</span>
    <span class="n">order</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">first_entry</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_string</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">first_entry</span><span class="p">):</span>
    <span class="k">assert</span> <span class="n">order</span> <span class="o">==</span> <span class="p">[</span><span class="n">first_entry</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">test_int</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">first_entry</span><span class="p">):</span>
    <span class="n">order</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">order</span> <span class="o">==</span> <span class="p">[</span><span class="n">first_entry</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span>
</code></pre></div>

<p>自动固件也可以像别的固件一样通过传参来使用，只是没这个必要。<br>
另外，由于不传参，所以测试函数是无法获取到固件的返回值的。上面例子里的自动固件就是没有返回值的。  </p>
<h3 id="gu-jian-de-scope-fan-wei"><a class="toclink" href="#gu-jian-de-scope-fan-wei">固件的scope范围</a></h3>
<p>这里scope并不是作用域，能引用固件的函数就能使用这个固件。<br>
scope定义的是fixture运行的情况。默认是function，在每个测试函数之前被调用一次。也可以修改为class，那么就是在class级别仅调用一次。<br>
scope的参数选项，scope会在测试首次请求时创建，之后根据其范围销毁:</p>
<ul>
<li>function 默认，测试结束是销毁，之后再要用就是创建一个新的。</li>
<li>class，在类的最后一个测试完成后，teardown 销毁。</li>
<li>module，在模块的最后一个测试完成后，teardown 销毁。</li>
<li>package，在包的最后一个测试完成后，teardown 销毁。</li>
<li>session，在最后一个session的test是销毁</li>
</ul>
<p>所以复用是通过销毁来控制的，不销毁之后就能继续用，当前的状态会传递下去。销毁了，之后再用就重新创建。<br>
比如测试前需要先建立连接，不过这个连接只要建立一次，之后一直使用这个连接即可。下面的例子定义了模块级别的固件。在模块中，第一个使用到该固件的测试之前会运行固件。之后不销毁，继续提供给模块中其他需要的测试函数使用。等所有使用该固件的测试函数都完成后会销毁这个固件。如果是其他模块中也有使用这个固件的测试，会创建一个新的使用：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>
<span class="kn">import</span> <span class="nn">smtplib</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&quot;module&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">smtp_connection</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTP</span><span class="p">(</span><span class="s2">&quot;smtp.gmail.com&quot;</span><span class="p">,</span> <span class="mi">587</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_ehlo</span><span class="p">(</span><span class="n">smtp_connection</span><span class="p">):</span>
    <span class="n">response</span><span class="p">,</span> <span class="n">msg</span> <span class="o">=</span> <span class="n">smtp_connection</span><span class="o">.</span><span class="n">ehlo</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">response</span> <span class="o">==</span> <span class="mi">250</span>
    <span class="k">assert</span> <span class="sa">b</span><span class="s2">&quot;smtp.gmail.com&quot;</span> <span class="ow">in</span> <span class="n">msg</span>
    <span class="k">assert</span> <span class="mi">0</span>  <span class="c1"># for demo purposes</span>

<span class="k">def</span> <span class="nf">test_noop</span><span class="p">(</span><span class="n">smtp_connection</span><span class="p">):</span>
    <span class="n">response</span><span class="p">,</span> <span class="n">msg</span> <span class="o">=</span> <span class="n">smtp_connection</span><span class="o">.</span><span class="n">noop</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">response</span> <span class="o">==</span> <span class="mi">250</span>
    <span class="k">assert</span> <span class="mi">0</span>  <span class="c1"># for demo purposes</span>
</code></pre></div>

<p>这里例子里可以考虑吧scope设置为session级别。  </p>
<h4 id="dong-tai-fan-wei"><a class="toclink" href="#dong-tai-fan-wei">动态范围</a></h4>
<p>可以不传上面的scope参数选项的字符串，而是传一个会返回以上结果的函数给scope实现动态指定范围。<br>
这个函数有两个参数，第一个是<code>fixture_name</code>字符串类型，第二个是<code>config</code>configuration对象。用法如下：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">determine_scope</span><span class="p">(</span><span class="n">fixture_name</span><span class="p">,</span> <span class="n">config</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">getoption</span><span class="p">(</span><span class="s2">&quot;--keep-containers&quot;</span><span class="p">,</span> <span class="kc">None</span><span class="p">):</span>
        <span class="k">return</span> <span class="s2">&quot;session&quot;</span>
    <span class="k">return</span> <span class="s2">&quot;function&quot;</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="n">determine_scope</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">docker_container</span><span class="p">():</span>
    <span class="k">yield</span> <span class="n">spawn_container</span><span class="p">()</span>
</code></pre></div>

<h3 id="gu-jian-qing-li-bu-zhou"><a class="toclink" href="#gu-jian-qing-li-bu-zhou">固件清理步骤</a></h3>
<p>固件使用完后，对象就会被销毁。有时候再销毁之前可能需要一些额外的清理步骤。比如建立的连接希望手动断开，或者创建的临时数据在固件销毁前删除掉。<br>
有两种方式来定制固件销毁前的清理步骤。  </p>
<h4 id="yield-tui-jian"><a class="toclink" href="#yield-tui-jian">yield（推荐）</a></h4>
<p>推荐这个是因为实现起来比较简单，但是会有隐患。不过如果结构简单合理，隐患是可以避免的。<br>
使用yield替代return来返回。这样销毁的时候会回到yield的位置，从而会执行yield后面的代码，运行到固件函数的最后返回时完成销毁。<br>
多个fixture需要销毁的时候，其顺序是按照后进先出的顺序进行销毁的。先创建的对象可能会向里添加之后创建的对象，这样就有依赖了。最后创建的对象销毁时，应该是没有依赖的，所以要先进行销毁。<br>
下面是一个简单的示例的样子：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># content of emaillib.py</span>
<span class="k">class</span> <span class="nc">MailAdminClient</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">create_user</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">MailUser</span><span class="p">()</span>

    <span class="k">def</span> <span class="nf">delete_user</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user</span><span class="p">):</span>
        <span class="c1"># do some cleanup</span>
        <span class="k">pass</span>


<span class="k">class</span> <span class="nc">MailUser</span><span class="p">:</span>
    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">inbox</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="k">def</span> <span class="nf">send_email</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">email</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
        <span class="n">other</span><span class="o">.</span><span class="n">inbox</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">email</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">clear_mailbox</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">inbox</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>


<span class="k">class</span> <span class="nc">Email</span><span class="p">:</span>
    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">subject</span><span class="p">,</span> <span class="n">body</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">subject</span> <span class="o">=</span> <span class="n">subject</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">body</span> <span class="o">=</span> <span class="n">body</span>


<span class="c1"># content of test_emaillib.py</span>
<span class="kn">import</span> <span class="nn">pytest</span>

<span class="kn">from</span> <span class="nn">emaillib</span> <span class="kn">import</span> <span class="n">Email</span><span class="p">,</span> <span class="n">MailAdminClient</span>


<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">mail_admin</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">MailAdminClient</span><span class="p">()</span>


<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">sending_user</span><span class="p">(</span><span class="n">mail_admin</span><span class="p">):</span>
    <span class="n">user</span> <span class="o">=</span> <span class="n">mail_admin</span><span class="o">.</span><span class="n">create_user</span><span class="p">()</span>
    <span class="k">yield</span> <span class="n">user</span>
    <span class="n">mail_admin</span><span class="o">.</span><span class="n">delete_user</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>


<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">receiving_user</span><span class="p">(</span><span class="n">mail_admin</span><span class="p">):</span>
    <span class="n">user</span> <span class="o">=</span> <span class="n">mail_admin</span><span class="o">.</span><span class="n">create_user</span><span class="p">()</span>
    <span class="k">yield</span> <span class="n">user</span>
    <span class="n">mail_admin</span><span class="o">.</span><span class="n">delete_user</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">test_email_received</span><span class="p">(</span><span class="n">sending_user</span><span class="p">,</span> <span class="n">receiving_user</span><span class="p">):</span>
    <span class="n">email</span> <span class="o">=</span> <span class="n">Email</span><span class="p">(</span><span class="n">subject</span><span class="o">=</span><span class="s2">&quot;Hey!&quot;</span><span class="p">,</span> <span class="n">body</span><span class="o">=</span><span class="s2">&quot;How&#39;s it going?&quot;</span><span class="p">)</span>
    <span class="n">sending_user</span><span class="o">.</span><span class="n">send_email</span><span class="p">(</span><span class="n">email</span><span class="p">,</span> <span class="n">receiving_user</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">email</span> <span class="ow">in</span> <span class="n">receiving_user</span><span class="o">.</span><span class="n">inbox</span>
</code></pre></div>

<h5 id="yi-chang-chu-li"><a class="toclink" href="#yi-chang-chu-li">异常处理</a></h5>
<p>这里要讲的就是上面提到的隐患。<br>
如果在yield之前抛出了异常，之后也不会再返回到yield的位置了，那么yield之后定义的步骤也不会被执行了。<br>
做好异常处理应该能避免这个问题，但是这里并不是要提醒注意处理异常，而是要提醒可能会发生函数中途抛出异常的情况。抛出异常后函数就会终止不再会继续执行，包括后面的销毁步骤。<br>
这里需要注意的是固件结构的简单合理，具体会在下一小节安全销毁固件展开。  </p>
<h4 id="tian-jia-zhong-jie-qi"><a class="toclink" href="#tian-jia-zhong-jie-qi">添加终结器</a></h4>
<p>这个方式要比yield复杂一些，但是解决了异常后销毁步骤不执行的问题。它是把销毁的自定义步骤定义为一个函数，终结器。然后把终结器添加到上下文中去，以保证终结器在添加完成之后，即使抛出异常也能被执行。终结器添加的位置应该紧跟在增加了需要手动销毁的资源的代码之后。<br>
还是上面的例子，使用终结器的写法：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># content of test_emaillib.py</span>
<span class="kn">import</span> <span class="nn">pytest</span>

<span class="kn">from</span> <span class="nn">emaillib</span> <span class="kn">import</span> <span class="n">Email</span><span class="p">,</span> <span class="n">MailAdminClient</span>


<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">mail_admin</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">MailAdminClient</span><span class="p">()</span>


<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">sending_user</span><span class="p">(</span><span class="n">mail_admin</span><span class="p">):</span>
    <span class="n">user</span> <span class="o">=</span> <span class="n">mail_admin</span><span class="o">.</span><span class="n">create_user</span><span class="p">()</span>
    <span class="k">yield</span> <span class="n">user</span>
    <span class="n">mail_admin</span><span class="o">.</span><span class="n">delete_user</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>


<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">receiving_user</span><span class="p">(</span><span class="n">mail_admin</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
    <span class="n">user</span> <span class="o">=</span> <span class="n">mail_admin</span><span class="o">.</span><span class="n">create_user</span><span class="p">()</span>

    <span class="k">def</span> <span class="nf">delete_user</span><span class="p">():</span>
        <span class="n">mail_admin</span><span class="o">.</span><span class="n">delete_user</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>

    <span class="n">request</span><span class="o">.</span><span class="n">addfinalizer</span><span class="p">(</span><span class="n">delete_user</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">user</span>


<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">email</span><span class="p">(</span><span class="n">sending_user</span><span class="p">,</span> <span class="n">receiving_user</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
    <span class="n">_email</span> <span class="o">=</span> <span class="n">Email</span><span class="p">(</span><span class="n">subject</span><span class="o">=</span><span class="s2">&quot;Hey!&quot;</span><span class="p">,</span> <span class="n">body</span><span class="o">=</span><span class="s2">&quot;How&#39;s it going?&quot;</span><span class="p">)</span>
    <span class="n">sending_user</span><span class="o">.</span><span class="n">send_email</span><span class="p">(</span><span class="n">_email</span><span class="p">,</span> <span class="n">receiving_user</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">empty_mailbox</span><span class="p">():</span>
        <span class="n">receiving_user</span><span class="o">.</span><span class="n">clear_mailbox</span><span class="p">()</span>

    <span class="n">request</span><span class="o">.</span><span class="n">addfinalizer</span><span class="p">(</span><span class="n">empty_mailbox</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">_email</span>


<span class="k">def</span> <span class="nf">test_email_received</span><span class="p">(</span><span class="n">receiving_user</span><span class="p">,</span> <span class="n">email</span><span class="p">):</span>
    <span class="k">assert</span> <span class="n">email</span> <span class="ow">in</span> <span class="n">receiving_user</span><span class="o">.</span><span class="n">inbox</span>
</code></pre></div>

<h4 id="an-quan-xiao-hui-gu-jian"><a class="toclink" href="#an-quan-xiao-hui-gu-jian">安全销毁固件</a></h4>
<p>不要把所有的步骤都放在一个固件中，这样只要其中有一个步骤异常了，所有的销毁步骤都不会发生，yield结构的情况。虽然这个问题可以通过改用终结器结构来解决。<br>
如果每个固件只做一个状态修改的操作，然后yield，之后是对应的销毁操作。那么异常发生的情况一般是操作失败，那么也就不需要执行yield后面的销毁操作。<br>
还有一个问题就是耦合了各个步骤后，也不方便在其他测试中重用。可能仅有部分步骤是一样的，但是有部分是不同的，这样就不能重用了。而如果分拆成多个，就可以选择需要的可重用的部分来使用。  </p>
<h3 id="duo-ge-ce-shi-de-tong-yong-gu-jian"><a class="toclink" href="#duo-ge-ce-shi-de-tong-yong-gu-jian">多个测试的通用固件</a></h3>
<p>有时会有一些通用的准备工作针对一定范围内所有的测试方法。可以加上autorun自动执行。还可以用scope设定好一个合适的范围。<br>
比如，在类里多个测试方法，测试前都需要登录。那就写一个登录的login方法，加上autorun，把范围scope指定为class，那么在所有测试方法执行前首先会完成登录。  </p>
<ul>
<li>设置为autorun，那么不需要显示的传参，测试前会自动完成登录</li>
<li>这里指定了scope范围是calss，所以登录的动作只会执行一次</li>
</ul>
<p>这个用法相当于unittest的setUp方法。如果还要unittest的tearDown方法来销毁，参考上面小结的yield或者终结器。<br>
下面是一个简单的示例：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="k">class</span> <span class="nc">TestLogin</span><span class="p">:</span>
    <span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&quot;class&quot;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">login</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;登录&#39;</span><span class="p">)</span>
        <span class="k">yield</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;退出&#39;</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">test_case1</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">assert</span> <span class="mi">0</span>

    <span class="k">def</span> <span class="nf">test_case2</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">assert</span> <span class="mi">0</span>

    <span class="k">def</span> <span class="nf">test_case3</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">assert</span> <span class="mi">0</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="vm">__file__</span><span class="p">])</span>
</code></pre></div>

<p>会在第一个测试时候，执行登录。然后在最后一个测试的时候，完成退出。<br>
下面还有一个usefixtures固件也有类似的功能，适合每个测试都需要独立的准备工作的情况（每次测试前都需要执行一次固件）。  </p>
<h3 id="gu-jian-zhong-tong-guo-shang-xia-wen-chuan-di-bian-liang-zhi"><a class="toclink" href="#gu-jian-zhong-tong-guo-shang-xia-wen-chuan-di-bian-liang-zhi">固件中通过上下文传递变量值</a></h3>
<p>固件可以通过request参数获取到测试的上下文内容。比如模块中的变量smtpserver，通过<code>getattr(request.module, "smtpserver", "smtp.gmail.com")</code>，可以动态获取到不同模块里不同的值。第二个参数是变量名称，第三个参数是默认值，缺省默认值是None。<br>
可以跨module获取到模块级的变量，可以获取值，也可以传值（下一小节）。比如在<code>conftest.py</code>里有一个初始化的固件，用这个方法可以在module里设置变量，也就是在py文件级别加一行smtpserver的变量赋值，这样在这个文件中加载固件的时候，固件会以当前文件的smtpserver的值执行固件的初始化。<br>
提到了functine和class，但是似乎没啥用或者用不来，也没例子。固件的scope参数的范围不影响module的使用。比如function级别的固件一样可以使用<code>request.module</code>。不过scope范围大于request的范围应该会有问题，这点没提到。<br>
这样可以动态的定义统一的固件模板，部分参数可以在模块中做修改：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># content of conftest.py</span>
<span class="kn">import</span> <span class="nn">pytest</span>
<span class="kn">import</span> <span class="nn">smtplib</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&quot;module&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">smtp_connection</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="c1"># 模块中可以修改smtpserver的值，默认是smtp.gmail.com</span>
    <span class="n">server</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">module</span><span class="p">,</span> <span class="s2">&quot;smtpserver&quot;</span><span class="p">,</span> <span class="s2">&quot;smtp.gmail.com&quot;</span><span class="p">)</span>
    <span class="n">smtp_connection</span> <span class="o">=</span> <span class="n">smtplib</span><span class="o">.</span><span class="n">SMTP</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="mi">587</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
    <span class="k">yield</span> <span class="n">smtp_connection</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;finalizing </span><span class="si">{}</span><span class="s2"> (</span><span class="si">{}</span><span class="s2">)&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">smtp_connection</span><span class="p">,</span> <span class="n">server</span><span class="p">))</span>
    <span class="n">smtp_connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>


<span class="c1"># content of test_anothersmtp.py</span>
<span class="n">smtpserver</span> <span class="o">=</span> <span class="s2">&quot;mail.python.org&quot;</span>  <span class="c1"># 定义当前模块的smtpserver的值</span>

<span class="k">def</span> <span class="nf">test_showhelo</span><span class="p">(</span><span class="n">smtp_connection</span><span class="p">):</span>
    <span class="k">assert</span> <span class="mi">0</span><span class="p">,</span> <span class="n">smtp_connection</span><span class="o">.</span><span class="n">helo</span><span class="p">()</span>
</code></pre></div>

<h3 id="ce-shi-han-shu-gei-gu-jian-chuan-zhi-jue-ding-fan-hui-zhi"><a class="toclink" href="#ce-shi-han-shu-gei-gu-jian-chuan-zhi-jue-ding-fan-hui-zhi">测试函数给固件传值决定返回值</a></h3>
<p>测试函数给固件传数据，固件可以根据传入的数据决定返回值。最后这个返回值传回给测试函数。<br>
<code>@pytest.mark.fixt_data(42)</code>，mark后面的那个名称就是变量名，但是mark还有一些内置的mark，这个名称不能和那些内置mark的名称冲突。括号里的值就是这个变量的值。<br>
示例如下：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">fixt</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="n">marker</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">node</span><span class="o">.</span><span class="n">get_closest_marker</span><span class="p">(</span><span class="s2">&quot;fixt_data&quot;</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">marker</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="c1"># Handle missing marker in some way...</span>
        <span class="n">data</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">data</span> <span class="o">=</span> <span class="n">marker</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>

    <span class="c1"># Do something with the data</span>
    <span class="k">return</span> <span class="n">data</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">fixt_data</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">test_fixt</span><span class="p">(</span><span class="n">fixt</span><span class="p">):</span>
    <span class="k">assert</span> <span class="n">fixt</span> <span class="o">==</span> <span class="mi">42</span>
</code></pre></div>

<h3 id="gong-chang-han-shu-gu-jian"><a class="toclink" href="#gong-chang-han-shu-gu-jian">工厂函数固件</a></h3>
<p>固件不直接返回值，而是返回一个可以得到值的工厂函数。这样复用起来更方便。这个模式是一个闭包的用法。<br>
函数可以用参数，这样根据参数每次返回的值也会不同。适合在一个测试中用同样的模式获取到不同的值。比如请求数据库的记录，可以返回一个获取记录的函数，传入不同的条件拿到不同的记录。  </p>
<div class="highlight"><pre><span></span><code><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">make_customer_record</span><span class="p">():</span>
    <span class="k">def</span> <span class="nf">_make_customer_record</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
        <span class="k">return</span> <span class="p">{</span><span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span> <span class="s2">&quot;orders&quot;</span><span class="p">:</span> <span class="p">[]}</span>

    <span class="k">return</span> <span class="n">_make_customer_record</span>

<span class="k">def</span> <span class="nf">test_customer_records</span><span class="p">(</span><span class="n">make_customer_record</span><span class="p">):</span>
    <span class="n">customer_1</span> <span class="o">=</span> <span class="n">make_customer_record</span><span class="p">(</span><span class="s2">&quot;Lisa&quot;</span><span class="p">)</span>
    <span class="n">customer_2</span> <span class="o">=</span> <span class="n">make_customer_record</span><span class="p">(</span><span class="s2">&quot;Mike&quot;</span><span class="p">)</span>
    <span class="n">customer_3</span> <span class="o">=</span> <span class="n">make_customer_record</span><span class="p">(</span><span class="s2">&quot;Meredith&quot;</span><span class="p">)</span>
</code></pre></div>

<p>下面的例子是在数据库中生成多条用于测试的临时记录。闭包内是生成一条记录并返回，返回前会把生成的记录保存在闭包外的一个列表中。闭包外有一个列表由于记录所有的临时记录，最后用yield在测试完成后遍历列表，逐条销毁临时创建的数据库记录。  </p>
<div class="highlight"><pre><span></span><code><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">make_customer_record</span><span class="p">():</span>

    <span class="n">created_records</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="k">def</span> <span class="nf">_make_customer_record</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
        <span class="n">record</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">Customer</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="n">orders</span><span class="o">=</span><span class="p">[])</span>
        <span class="n">created_records</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">record</span>

    <span class="k">yield</span> <span class="n">_make_customer_record</span>

    <span class="k">for</span> <span class="n">record</span> <span class="ow">in</span> <span class="n">created_records</span><span class="p">:</span>
        <span class="n">record</span><span class="o">.</span><span class="n">destroy</span><span class="p">()</span>


<span class="k">def</span> <span class="nf">test_customer_records</span><span class="p">(</span><span class="n">make_customer_record</span><span class="p">):</span>
    <span class="n">customer_1</span> <span class="o">=</span> <span class="n">make_customer_record</span><span class="p">(</span><span class="s2">&quot;Lisa&quot;</span><span class="p">)</span>
    <span class="n">customer_2</span> <span class="o">=</span> <span class="n">make_customer_record</span><span class="p">(</span><span class="s2">&quot;Mike&quot;</span><span class="p">)</span>
    <span class="n">customer_3</span> <span class="o">=</span> <span class="n">make_customer_record</span><span class="p">(</span><span class="s2">&quot;Meredith&quot;</span><span class="p">)</span>
</code></pre></div>

<h3 id="parametrizing-fixtures-can-shu-hua-gu-jian"><a class="toclink" href="#parametrizing-fixtures-can-shu-hua-gu-jian">Parametrizing fixtures 参数化固件</a></h3>
<p>可以给固件参数化，那么每个引用这个固件的测试，会对每个参数执行一次完整的测试。<br>
params，定制测试的参数。函数中通过request.param获取到当前测试使用的值：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span> 

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">params</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;www.pytest.org&quot;</span><span class="p">,</span> <span class="s2">&quot;www.python.org&quot;</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">get_site</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">param</span>

<span class="k">def</span> <span class="nf">test_www</span><span class="p">(</span><span class="n">get_site</span><span class="p">):</span>
    <span class="k">assert</span> <span class="s1">&#39;www.&#39;</span> <span class="ow">in</span> <span class="n">get_site</span>

<span class="k">def</span> <span class="nf">test_org</span><span class="p">(</span><span class="n">get_site</span><span class="p">):</span>
    <span class="k">assert</span> <span class="s1">&#39;.org&#39;</span> <span class="ow">in</span> <span class="n">get_site</span>
</code></pre></div>

<p>还能定制测试的id，默认的id会使用参数化的值，可以用ids参数替换为更有意义的字符串:</p>
<div class="highlight"><pre><span></span><code><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">params</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;www.pytest.org&quot;</span><span class="p">,</span> <span class="s2">&quot;www.python.org&quot;</span><span class="p">],</span> <span class="n">ids</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;pytest&#39;</span><span class="p">,</span> <span class="s1">&#39;python&#39;</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">get_site</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">param</span>
</code></pre></div>

<p>ids参数还能传一个函数来动态获取到需要替换的参数化的名称：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">params</span><span class="o">=</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="n">ids</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;spam&quot;</span><span class="p">,</span> <span class="s2">&quot;ham&quot;</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">a</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">param</span>

<span class="k">def</span> <span class="nf">test_a</span><span class="p">(</span><span class="n">a</span><span class="p">):</span>
    <span class="k">pass</span>

<span class="k">def</span> <span class="nf">idfn</span><span class="p">(</span><span class="n">fixture_value</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">fixture_value</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
        <span class="k">return</span> <span class="s2">&quot;eggs&quot;</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">return</span> <span class="kc">None</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">params</span><span class="o">=</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="n">ids</span><span class="o">=</span><span class="n">idfn</span><span class="p">)</span>  <span class="c1"># ids=[&quot;eggs&quot;, 1]</span>
<span class="k">def</span> <span class="nf">b</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">param</span>

<span class="k">def</span> <span class="nf">test_b</span><span class="p">(</span><span class="n">b</span><span class="p">):</span>
    <span class="k">pass</span>
</code></pre></div>

<h4 id="pytestparam"><a class="toclink" href="#pytestparam">pytest.param()</a></h4>
<p>上面的例子是分别使用params和ids参数传递value和id的列表。可以用<code>pytest.param()</code>生成的对象作为params的元素，对象可以包含完整的value、mark、id信息。value是必须的，另外两个属性可以缺省。<br>
对上面的第一个例子可以修改为如下形式：</p>
<div class="highlight"><pre><span></span><code><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">params</span><span class="o">=</span><span class="p">[</span><span class="n">pytest</span><span class="o">.</span><span class="n">param</span><span class="p">(</span><span class="s2">&quot;www.pytest.org&quot;</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="s1">&#39;pytest&#39;</span><span class="p">),</span> <span class="n">pytest</span><span class="o">.</span><span class="n">param</span><span class="p">(</span><span class="s2">&quot;www.python.org&quot;</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="s1">&#39;python&#39;</span><span class="p">)])</span>
<span class="k">def</span> <span class="nf">get_site</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">param</span>
</code></pre></div>

<p>关于mark的作用，还未了解到，用法没有什么差别，示例如下：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">params</span><span class="o">=</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">pytest</span><span class="o">.</span><span class="n">param</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="n">marks</span><span class="o">=</span><span class="n">pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">skip</span><span class="p">)])</span>
<span class="k">def</span> <span class="nf">data_set</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">param</span>

<span class="k">def</span> <span class="nf">test_data</span><span class="p">(</span><span class="n">data_set</span><span class="p">):</span>
    <span class="k">pass</span>
</code></pre></div>

<p>这里的效果是，测试结果是一个skipped的标记，而不是成功或失败，无论该测试是否成功。  </p>
<h3 id="usefixtures-gu-jian"><a class="toclink" href="#usefixtures-gu-jian">usefixtures固件</a></h3>
<p>为类的class加上这个装饰器，可以使得类里的每一个测试运行前都会先执行这个固件。但是测试函数无法获取到返回值。<br>
使用场景就是定制统一的准备步骤，但是不需要去访问它，也就是做过就好了，比如环境的设置。每个测试函数执行前都会重新运行一次固件。这个和建立连接不同，都是所有测试都需要的前置步骤，建立连接是只要执行一次，这个方式是每个测试前都要重新执行一次。  </p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">tempfile</span>

<span class="kn">import</span> <span class="nn">pytest</span>

<span class="c1"># 固件，清理资源</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">cleandir</span><span class="p">():</span>
    <span class="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">newpath</span><span class="p">:</span>
        <span class="n">old_cwd</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">()</span>
        <span class="n">os</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="n">newpath</span><span class="p">)</span>
        <span class="k">yield</span>
        <span class="n">os</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="n">old_cwd</span><span class="p">)</span>

<span class="c1"># 这个类下的所有的测试函数都需要执行一次cleandir固件</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">usefixtures</span><span class="p">(</span><span class="s2">&quot;cleandir&quot;</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">TestDirectoryInit</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_cwd_starts_empty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">assert</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span> <span class="o">==</span> <span class="p">[]</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&quot;myfile&quot;</span><span class="p">,</span> <span class="s2">&quot;w&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
            <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&quot;hello&quot;</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">test_cwd_again_starts_empty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">assert</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span> <span class="o">==</span> <span class="p">[]</span>
</code></pre></div>

<h4 id="duo-ge-usefixtures-gu-jian"><a class="toclink" href="#duo-ge-usefixtures-gu-jian">多个usefixtures固件</a></h4>
<p>不需要叠上多个装饰器，而是把每个固件的的名称依次作为位置参数传入：</p>
<div class="highlight"><pre><span></span><code><span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">usefixtures</span><span class="p">(</span><span class="s2">&quot;cleandir&quot;</span><span class="p">,</span> <span class="s2">&quot;anotherfixture1&quot;</span><span class="p">,</span> <span class="s2">&quot;anotherfixture2&quot;</span><span class="p">)</span>
</code></pre></div>

<h4 id="mo-kuai-ji-bie-shi-yong"><a class="toclink" href="#mo-kuai-ji-bie-shi-yong">模块级别使用</a></h4>
<p>可以把范围扩大到模块级别，在模块中添加如下的一行语句即可，语句的位置没有要求：</p>
<div class="highlight"><pre><span></span><code><span class="n">pytestmark</span> <span class="o">=</span> <span class="n">pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">usefixtures</span><span class="p">(</span><span class="s2">&quot;cleandir&quot;</span><span class="p">)</span>
</code></pre></div>

<p>下面的例子只为了演示和说明问题，不是测试的正确的用法，因为运行单个测试和整体运行结果是不一样的。注释是根据整体运行的情况添加的：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="n">ORDER</span> <span class="o">=</span> <span class="p">[]</span>  <span class="c1"># 全局变量，固件向列表添加元素</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">append_one</span><span class="p">():</span>
    <span class="n">ORDER</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">usefixtures</span><span class="p">(</span><span class="s2">&quot;append_one&quot;</span><span class="p">)</span>  <span class="c1"># 有了模块级别的设置，这个类级别的设置有没有都一样了</span>
<span class="k">class</span> <span class="nc">TestOrder</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_one</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">assert</span> <span class="n">ORDER</span> <span class="o">==</span> <span class="p">[</span><span class="mi">1</span><span class="p">]</span>  <span class="c1"># 执行append_one固件，添加了1个元素</span>

    <span class="k">def</span> <span class="nf">test_two</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>     
        <span class="k">assert</span> <span class="n">ORDER</span> <span class="o">==</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>  <span class="c1"># 执行append_one固件，又添加了1个元素</span>

<span class="k">def</span> <span class="nf">test_three</span><span class="p">():</span>
    <span class="k">assert</span> <span class="n">ORDER</span> <span class="o">==</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>  <span class="c1"># 执行append_one固件，共添加了3个元素</span>

<span class="n">pytestmark</span> <span class="o">=</span> <span class="n">pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">usefixtures</span><span class="p">(</span><span class="s2">&quot;append_one&quot;</span><span class="p">)</span>  <span class="c1"># 这行语句放在哪里没区别</span>
</code></pre></div>

<h3 id="ge-ge-ji-bie-dui-gu-jian-de-fu-gai"><a class="toclink" href="#ge-ge-ji-bie-dui-gu-jian-de-fu-gai">各个级别对固件的覆盖</a></h3>
<p>可以在一个大的范围里定义固件，之后在一个较小的范围里定义一个同名固件，实现定制化。当前这个小范围使用对这个小范围定制的固件版本。  </p>
<h4 id="wen-jian-jia-ji-bie-fu-gai"><a class="toclink" href="#wen-jian-jia-ji-bie-fu-gai">文件夹级别覆盖</a></h4>
<p>子文件夹有和父文件夹同名的固件，子文件夹中的测试函数会使用子文件夹的固件。  </p>
<h4 id="mo-kuai-ji-bie-fu-gai"><a class="toclink" href="#mo-kuai-ji-bie-fu-gai">模块级别覆盖</a></h4>
<p>当前文件夹的<code>conftest.py</code>有一个固件，会被当前模块(module)的测试使用。<br>
在当前module的py文件中，创建一个同名的固件，当前py文件的测试都会使用文件中的这个固件。  </p>
<h4 id="fu-gai-gu-jian-de-fan-hui-zhi"><a class="toclink" href="#fu-gai-gu-jian-de-fan-hui-zhi">覆盖固件的返回值</a></h4>
<p>直接覆盖掉固件的返回值，可以覆盖掉被间接使用的固件的返回值：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">username</span><span class="p">():</span>
    <span class="k">return</span> <span class="s1">&#39;any name&#39;</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">other_username</span><span class="p">(</span><span class="n">username</span><span class="p">):</span>
    <span class="k">return</span> <span class="s1">&#39;other-&#39;</span> <span class="o">+</span> <span class="n">username</span>

<span class="c1"># 覆盖了固件username的返回值</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">&#39;username&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;directly-overridden-username&#39;</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">test_username</span><span class="p">(</span><span class="n">username</span><span class="p">):</span>
    <span class="k">assert</span> <span class="n">username</span> <span class="o">==</span> <span class="s1">&#39;directly-overridden-username&#39;</span>

<span class="c1"># 没有直接使用固件username，但是username的值依然被覆盖掉了</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">&#39;username&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;indirectly-overridden-username&#39;</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">test_username_other</span><span class="p">(</span><span class="n">other_username</span><span class="p">):</span>
    <span class="k">assert</span> <span class="n">other_username</span> <span class="o">==</span> <span class="s1">&#39;other-indirectly-overridden-username&#39;</span>
</code></pre></div>

<h4 id="can-shu-hua-de-gu-jian-he-fei-can-shu-hua-gu-jian-de-hu-xiang-fu-gai-de-qing-kuang"><a class="toclink" href="#can-shu-hua-de-gu-jian-he-fei-can-shu-hua-gu-jian-de-hu-xiang-fu-gai-de-qing-kuang">参数化的固件和非参数化固件的互相覆盖的情况</a></h4>
<p>依然是上面文件夹或者是模块级别覆盖，同名固件覆盖的情况。只要固件同名同名，参数化和非参数化的固件也是可以互相覆盖的。<br>
参数化的固件，会对每个参数各调用一次。非参数化的固件就只调用一次。非参数化的固件可以用上面的方式覆盖掉参数化固件，这样就只会调用一次了。反之亦然。  </p>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>